﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;

namespace ModuleSettings
{
    /// <summary>
    /// a manager class to manage the user config files of a .net application (local and roaming)
    /// it uses the default configration files of .net applications like
    /// C:\users\[user name]\appData\[local or roaming]\[company name]\[version]\user.config
    /// </summary>
    public class XmlUserSettingsRepo : IUserSettingsRepository
    {
        #region Fields

        private string configFilePath;
        private XDocument doc;
        private XElement root;
        private XElement specialSettingsElement;
        private string specialSettingsName;
        private XElement userSettingsElement;

        #endregion

        #region Methods

        /// <summary>
        /// get the all in the current config file defined settings names
        /// </summary>
        /// <returns>a list of names of all settings defined in the current config file</returns>
        public IList<string> GetAllSettingNames()
        {
            if (specialSettingsElement == null) return new List<string>();
            return (from settingElement in specialSettingsElement.Elements("setting") select settingElement.Attribute("name").Value).ToList();
        }

        /// <summary>
        /// get the value of a user settings with the given settings name
        /// </summary>
        /// <param name="settingName">the name of the settings to get the user defined value</param>
        /// <returns>the current value of the user settings with the given name</returns>
        public object GetSettingValue(string settingName)
        {
            if (specialSettingsElement == null) return null;
            XElement settingElement = GetSettingElement(settingName);
            if (settingElement == null) return null;
            return settingElement.Element("value").Value;
        }

        /// <summary>
        /// load a well formed user config file of .net application
        /// </summary>
        /// <param name="configFilePath">the path the user config file</param>
        /// <param name="specialSettingsName">the special settings name for a settings class</param>
        public void Load(string configFilePath, string specialSettingsName)
        {
            this.configFilePath = configFilePath;
            this.specialSettingsName = specialSettingsName;
            if (!File.Exists(configFilePath))
            {
                //this.configFilePath = null;
                doc = null;
                root = null;
                userSettingsElement = null;
                specialSettingsElement = null;
                return;
            }
            else
            {
                doc = XDocument.Load(configFilePath);
                root = doc.Root;
                userSettingsElement = GetUserSettingsElement();
                specialSettingsElement = userSettingsElement == null ?
                                         null :
                                         GetSpecialSettingsElement(this.specialSettingsName);
            }
        }
        /// <summary>
        /// save all user defined values of user settings in the current user config file
        /// </summary>
        public void Save()
        {
            if (configFilePath == null) throw new ArgumentNullException("no config file path be defined");
            if (doc == null) CreateSpecialSettingsElement();
            //RegisterSpecialUserSettingsSection(specialSettingsName);
            var dir = Path.GetDirectoryName(configFilePath);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            if (!File.Exists(configFilePath))
            {
                File.Create(configFilePath).Close();
            }
            doc.Save(configFilePath);
        }
        /// <summary>
        /// set the value of a user settings with the given settings name
        /// </summary>
        /// <param name="settingName">the name of the settings to set the new value</param>
        /// <param name="value">a new value of the settings with the given settings name. 
        /// when value == null, then remove the current value of this settings and reset the default value for this settings</param>
        public void SetSettingValue(string settingName, object value)
        {
            XElement settingElement = GetSettingElement(settingName);
            if (value == null)
            {
                if (settingElement != null)
                    settingElement.Remove();
                return;
            }
            if (settingElement == null)
            {
                string elementValue = value.ToString();
                CreateSettingElement(settingName, elementValue);
                return;
            }
            settingElement.SetElementValue("value", value);
        }

        /// <summary>
        /// create the root element of a config file for a .net project
        /// </summary>
        /// <returns></returns>
        private XElement CreateConfigRootElement()
        {
            root = new XElement("configuration");
            if (doc == null)
                doc = CreateDocument();
            //doc.Add(root);
            return root;
        }
        /// <summary>
        /// create a new XDocument instance
        /// </summary>
        /// <returns></returns>
        private XDocument CreateDocument()
        {
            doc = new XDocument(root);
            return doc;
        }

        /// <summary>
        /// create a standard "userSettings" SectionGroup element of a config file for a .net project
        /// </summary>
        /// <returns>a new "userSettings" SectionGroup with the default format</returns>
        private XElement CreateSectionGroupElement()
        {
            XElement newSectionGroup = new XElement("sectionGroup");
            newSectionGroup.Add(new XAttribute("name", "userSettings"));
            newSectionGroup.Add(new XAttribute("type", "System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"));
            return newSectionGroup;
        }

        /// <summary>
        /// create a setting element for a settings with the given name and value
        /// </summary>
        /// <param name="settingName">the name of the new settings element</param>
        /// <param name="value">the value of the new settings element</param>
        /// <returns>a new XElement instance with the given name and value</returns>
        private XElement CreateSettingElement(string settingName, string value)
        {
            XElement settingElement = new XElement("setting");
            settingElement.SetAttributeValue("name", settingName);
            settingElement.SetAttributeValue("serializeAs", "String");
            settingElement.SetElementValue("value", value);
            if (specialSettingsElement == null)
                specialSettingsElement = CreateSpecialSettingsElement();
            specialSettingsElement.Add(settingElement);
            return settingElement;
        }

        /// <summary>
        /// create a new section element for the special settings with the given name
        /// </summary>
        /// <param name="specialSettingsName">the specail name of a settings</param>
        /// <returns>a new section element for a special settings</returns>
        private XElement CreateSpecialSettingSection(string specialSettingsName)
        {
            XElement specialSettingsSection = new XElement("section");
            specialSettingsSection.Add(new XAttribute("name", specialSettingsName));
            specialSettingsSection.Add(new XAttribute("type", "System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"));
            specialSettingsSection.Add(new XAttribute("allowExeDefinition", "MachineToLocalUser"));
            specialSettingsSection.Add(new XAttribute("requirePermission", "false"));
            return specialSettingsSection;
        }

        /// <summary>
        /// create a special settings element of a config file for a .net project
        /// </summary>
        /// <returns></returns>
        private XElement CreateSpecialSettingsElement()
        {
            specialSettingsElement = new XElement(specialSettingsName);
            if (userSettingsElement == null)
                userSettingsElement = CreateUserSettingsElement();
            userSettingsElement.Add(specialSettingsElement);
            RegisterSpecialUserSettingsSection(specialSettingsName);
            return specialSettingsElement;
        }
        /// <summary>
        /// create a userSettings element of a config file for a .net project
        /// </summary>
        /// <returns></returns>
        private XElement CreateUserSettingsElement()
        {
            userSettingsElement = new XElement("userSettings");
            if (root == null)
                root = CreateConfigRootElement();
            root.Add(userSettingsElement);
            return userSettingsElement;
        }

        /// <summary>
        /// get all configSections elements of a config file of a .net project
        /// </summary>
        /// <returns>all defined configSections elements</returns>
        private IEnumerable<XElement> GetConfigSectionElements()
        {
            var qConfigSections = from configSectionsElement in root.Elements("configSections")
                                  select configSectionsElement;
            if (qConfigSections.Count() == 0)
            {
                XElement configSectionElement = new XElement("configSections");
                root.AddFirst(configSectionElement);
                qConfigSections = from configSectionsElement in root.Elements("configSections")
                                  select configSectionsElement;
            }
            return qConfigSections;
        }
        /// <summary>
        /// get all "userSettings" SectionGroup elements from a config file of a .net project
        /// </summary>
        /// <param name="configSectionsElements">all configSections elements which can contain "userSettings" sectionGroup elements</param>
        /// <returns>all SectionGroup elements in the given configSections elements with the name "userSettings"</returns>
        private List<XElement> GetSectionGroupElements(IEnumerable<XElement> configSectionsElements)
        {
            List<XElement> sectionGroupElements = new List<XElement>();
            foreach (XElement configSections in configSectionsElements)
            {
                var qSectionGroupElement = from sectionGroupElement in configSections.Elements("sectionGroup")
                                           where sectionGroupElement.Attribute("name") != null &&
                                                 sectionGroupElement.Attribute("name").Value == "userSettings"
                                           select sectionGroupElement;
                sectionGroupElements.AddRange(qSectionGroupElement.ToList());
            }
            return sectionGroupElements;
        }

        /// <summary>
        /// get the XElement instance for the given settings name
        /// </summary>
        /// <param name="settingName">the name of a settings entity</param>
        /// <returns>the XElement instance with the given name, when not exist, return null</returns>
        private XElement GetSettingElement(string settingName)
        {
            if (specialSettingsElement == null) return null;
            var qSettingElement = from settingElement in specialSettingsElement.Elements("setting")
                                  where settingElement.Attribute("name").Value == settingName
                                  select settingElement;
            return qSettingElement.Count() == 0 ? null : qSettingElement.First();
        }

        /// <summary>
        /// get a special settings element for the given name
        /// </summary>
        /// <param name="specialSettingsName">the name of a settings element</param>
        /// <returns>the Xelement instance with the given name, when not exist, return null</returns>
        private XElement GetSpecialSettingsElement(string specialSettingsName)
        {
            var qSpecialSettings = from specialSettinElement in userSettingsElement.Descendants(specialSettingsName)
                                   select specialSettinElement;
            return qSpecialSettings.Count() == 0 ? null : qSpecialSettings.First();
        }

        /// <summary>
        /// get the special settingsSection with the given sepcialSettingsName in the given sectionGroup Elements
        /// </summary>
        /// <param name="sectionGroupElements">a set of sectionGroup elements with the name "userSettings"</param>
        /// <param name="specialSettingsName">the special name of a set of settings </param>
        /// <returns>the settingsSection element for the special settings with the given settings name</returns>
        private XElement GetSpecialSettingsSection(IEnumerable<XElement> sectionGroupElements, string specialSettingsName)
        {
            foreach (XElement sectionGroup in sectionGroupElements)
            {
                var qSectionElement = from sectionElement in sectionGroup.Elements("section")
                                      where sectionElement.Attribute("name") != null && sectionElement.Attribute("name").Value == specialSettingsName
                                      select sectionElement;
                if (qSectionElement.Count() != 0)
                {
                    return qSectionElement.First();
                }
            }
            return null;
        }

        /// <summary>
        /// get the userSettings element in the current config file
        /// </summary>
        /// <returns>the userSettings element defined in the current config file</returns>
        private XElement GetUserSettingsElement()
        {
            var qSettingsElement = from userSettingsElement in root.Descendants("userSettings")
                                   select userSettingsElement;
            return qSettingsElement.Count() == 0 ? null : qSettingsElement.First();
        }

        /// <summary>
        /// register the section element 
        /// (in the path: configSections\sectionGroup[@name=userSettings]\section[@name= the given special name]) 
        /// in the current config file of a .net project with the given name
        /// </summary>
        /// <param name="specailSettingsName">the name of the new section element in the current config file</param>
        private void RegisterSpecialUserSettingsSection(string specailSettingsName)
        {
            if (string.IsNullOrEmpty(specailSettingsName)) return;
            IEnumerable<XElement> qConfigSections = GetConfigSectionElements();

            List<XElement> sectionGroupElements = GetSectionGroupElements(qConfigSections);
            if (sectionGroupElements.Count == 0)
            {
                XElement configSectionElement = qConfigSections.First();
                XElement newSectionGroup = CreateSectionGroupElement();
                configSectionElement.Add(newSectionGroup);
                sectionGroupElements.Add(newSectionGroup);
            }

            XElement specialSettingsSection = GetSpecialSettingsSection(sectionGroupElements, specialSettingsName);
            if (specialSettingsSection == null)
            {
                specialSettingsSection = CreateSpecialSettingSection(specialSettingsName);
                sectionGroupElements.First().Add(specialSettingsSection);
            }
        }

        #endregion
    }
}
